/*
**************************************************************************************
*Myriad Engine                                                                       *
*Copyright (C) 2006-2007, 5d Studios (www.5d-Studios.com)                            *
*                                                                                    *
*This library is free software; you can redistribute it and/or                       *
*modify it under the terms of the GNU Lesser General Public                          *
*License as published by the Free Software Foundation; either                        *
*version 2.1 of the License, or (at your option) any later version.                  *
*                                                                                    *
*This library is distributed in the hope that it will be useful,                     *
*but WITHOUT ANY WARRANTY; without even the implied warranty of                      *
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                   *
*Lesser General Public License for more details.                                     *
*                                                                                    *
*You should have received a copy of the GNU Lesser General Public                    *
*License along with this library; if not, write to the Free Software                 *
*Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA      *
**************************************************************************************
*/
package leola.frontend.parsers;

import static leola.frontend.tokens.LeolaTokenType.BITWISE_AND;
import static leola.frontend.tokens.LeolaTokenType.BITWISE_NOT;
import static leola.frontend.tokens.LeolaTokenType.BITWISE_OR;
import static leola.frontend.tokens.LeolaTokenType.BITWISE_XOR;
import static leola.frontend.tokens.LeolaTokenType.DEF;
import static leola.frontend.tokens.LeolaTokenType.D_EQUALS;
import static leola.frontend.tokens.LeolaTokenType.GREATER_EQUALS;
import static leola.frontend.tokens.LeolaTokenType.GREATER_THAN;
import static leola.frontend.tokens.LeolaTokenType.IDENTIFIER;
import static leola.frontend.tokens.LeolaTokenType.INTEGER;
import static leola.frontend.tokens.LeolaTokenType.LEFT_BRACKET;
import static leola.frontend.tokens.LeolaTokenType.LEFT_PAREN;
import static leola.frontend.tokens.LeolaTokenType.LESS_EQUALS;
import static leola.frontend.tokens.LeolaTokenType.LESS_THAN;
import static leola.frontend.tokens.LeolaTokenType.LOGICAL_AND;
import static leola.frontend.tokens.LeolaTokenType.LOGICAL_OR;
import static leola.frontend.tokens.LeolaTokenType.MINUS;
import static leola.frontend.tokens.LeolaTokenType.MOD;
import static leola.frontend.tokens.LeolaTokenType.NOT;
import static leola.frontend.tokens.LeolaTokenType.NOT_EQUALS;
import static leola.frontend.tokens.LeolaTokenType.NULL;
import static leola.frontend.tokens.LeolaTokenType.PLUS;
import static leola.frontend.tokens.LeolaTokenType.REAL;
import static leola.frontend.tokens.LeolaTokenType.SLASH;
import static leola.frontend.tokens.LeolaTokenType.STAR;
import static leola.frontend.tokens.LeolaTokenType.STRING;
import static leola.frontend.tokens.LeolaTokenType.*;

import java.util.EnumSet;

import leola.ast.ASTNode;
import leola.ast.BinaryExpr;
import leola.ast.BooleanExpr;
import leola.ast.ChainedFuncInvocationExpr;
import leola.ast.CompoundExpr;
import leola.ast.Expr;
import leola.ast.NumberExpr;
import leola.ast.StringExpr;
import leola.ast.UnaryExpr;
import leola.ast.VarExpr;
import leola.ast.UnaryExpr.UnaryOp;
import leola.frontend.LeolaParser;
import leola.frontend.Token;
import leola.frontend.tokens.LeolaErrorCode;
import leola.frontend.tokens.LeolaTokenType;

/**
 * @author Tony
 *
 */
public class ExprParser extends StmtParser {

    // Synchronization set for starting an expression.
    public static final EnumSet<LeolaTokenType> EXPR_START_SET =
        EnumSet.of(PLUS, MINUS, IDENTIFIER, INTEGER, REAL, STRING, DEF, 
        		   NULL, NOT, BITWISE_NOT, LEFT_PAREN, LEFT_BRACKET);
	
    // Set of relational operators.
    private static final EnumSet<LeolaTokenType> REL_OPS =
        EnumSet.of(D_EQUALS, NOT_EQUALS, LESS_THAN, LESS_EQUALS,
                   GREATER_THAN, GREATER_EQUALS);
    
    // Set of multiplicative operators.
    private static final EnumSet<LeolaTokenType> MULT_OPS =
        EnumSet.of(STAR, SLASH, MOD, LOGICAL_AND, BITWISE_AND, BITWISE_OR,
        		   BIT_SHIFT_LEFT, BIT_SHIFT_RIGHT);
    
    // Set of additive operators.
    private static final EnumSet<LeolaTokenType> ADD_OPS =
        EnumSet.of(PLUS, MINUS, LOGICAL_OR, BITWISE_XOR);

    // set of tokens that represent a chained expression
    private static final EnumSet<LeolaTokenType> CHAINED_OP =
        EnumSet.of(DOT, LEFT_PAREN, LEFT_BRACKET/*, IDENTIFIER*/);
    
	
	/**
	 * @param parser
	 */
	public ExprParser(LeolaParser parser) {
		super(parser);
	}
	
	/**
     * Parse an expression.
     * To be overridden by the specialized statement parser subclasses.
     * @param token the initial token.
     * @return the root node of the generated parse tree.
     * @throws Exception if an error occurred.
     */
    public ASTNode parse(Token token)
        throws Exception
    {
        ASTNode statementNode = parseExpression(token);
        return statementNode;
    }
    
    /**
     * Parse an expression.
     * @param token the initial token.
     * @return the root node of the generated parse tree.
     * @throws Exception if an error occurred.
     */
    private ASTNode parseExpression(Token token)
        throws Exception
    {
        // Parse a simple expression and make the root of its tree
        // the root node.
        ASTNode rootNode = parseSimpleExpression(token);

        token = currentToken();
        LeolaTokenType tokenType = token.getType();

        // Look for a relational operator.
        if (REL_OPS.contains(tokenType)) {

            token = nextToken();  // consume the operator

            // Parse the second simple expression.  The operator node adopts
            // the simple expression's tree as its second child.
            ASTNode simExprNode = parseSimpleExpression(token);

            BinaryExpr bExpr = new BinaryExpr( (Expr)rootNode
            								  ,(Expr)simExprNode
            								  , tokenType.toBinaryOp() );
            
            // The operator node becomes the new root node.
            rootNode = bExpr;
        }
        else if (CHAINED_OP.contains(tokenType) ) {
                	
        	CompoundExpr compoundExpr = new CompoundExpr();
        	compoundExpr.addChild(rootNode);
        	rootNode = compoundExpr;
        	
	        do {	
	        		        
	            // Parse the second simple expression.  The operator node adopts
	            // the simple expression's tree as its second child.
	            ASTNode simExprNode = parseChainedExpr(token);
	            compoundExpr.addChild(simExprNode);
	            
	            token = currentToken();
	            tokenType = token.getType();
        	} while( ( CHAINED_OP.contains(tokenType) ||
        			   IDENTIFIER.equals(tokenType) ) && 
        			!tokenType.equals(LeolaTokenType.END_OF_FILE));
        	
        }
        return rootNode;
    }
    
    /**
     * Parses an Expression
     * @param token
     * @return
     * @throws Exception
     */
    private ASTNode parseSimpleExpression(Token token)
    	throws Exception {
        
        LeolaTokenType signType = null;  // type of leading sign (if any)

        // Look for a leading + or - sign.
        LeolaTokenType tokenType = token.getType();
        if ( (tokenType == LeolaTokenType.PLUS) || 
        	 (tokenType == LeolaTokenType.MINUS)) {
            signType = tokenType;            
            token = nextToken();  // consume the + or -
        }

        // Parse a term and make the root of its tree the root node.
        ASTNode rootNode = parseTerm(token);

        // Was there a leading - sign?
        if (signType == MINUS) {

            // Create a NEGATE node and adopt the current tree
            // as its child. The NEGATE node becomes the new root node.            
            UnaryExpr negateNode = new UnaryExpr((Expr)rootNode, UnaryOp.NEGATE);
            rootNode = negateNode;
        }

        token = currentToken();
        tokenType = token.getType();

        // Loop over additive operators.
        while (ADD_OPS.contains(tokenType)) {
            LeolaTokenType operator = tokenType;

            token = nextToken();  // consume the operator

            // Parse another term.  The operator node adopts
            // the term's tree as its second child.
            ASTNode termNode = parseTerm(token);

            BinaryExpr bExpr = new BinaryExpr( (Expr)rootNode
            		                         , (Expr)termNode
            		                         , operator.toBinaryOp());
            
            // The operator node becomes the new root node.
            rootNode = bExpr;

            token = currentToken();
            tokenType = token.getType();
        }

        return rootNode;
    }
    
    /**
     * Parse a term.
     * @param token the initial token.
     * @return the root node of the generated parse tree.
     * @throws Exception if an error occurred.
     */
    private ASTNode parseTerm(Token token) throws Exception
    {
        // Parse a factor and make its node the root node.
        ASTNode rootNode = parseFactor(token);
        
        token = currentToken();
        LeolaTokenType tokenType = token.getType();

        // Loop over multiplicative operators.
        while (MULT_OPS.contains(tokenType)) {
            LeolaTokenType operator = tokenType;
           
            token = nextToken();  // consume the operator

            // Parse another factor.  The operator node adopts
            // the term's tree as its second child.
            ASTNode factorNode = parseFactor(token);
            
            BinaryExpr bExpr = new BinaryExpr( (Expr)rootNode
            								 , (Expr)factorNode
            								 , operator.toBinaryOp());
            
            // The operator node becomes the new root node.
            rootNode = bExpr;

            token = currentToken();
            tokenType = token.getType();
        }

        return rootNode;
    }

    
    /**
     * Parse a factor.
     * @param token the initial token.
     * @return the root node of the generated parse tree.
     * @throws Exception if an error occurred.
     */
    private ASTNode parseFactor(Token token)
        throws Exception
    {
        LeolaTokenType tokenType = token.getType();
        ASTNode rootNode = null;

        switch (tokenType) {

            case IDENTIFIER: {
                return parseIdentifier(token);
            }

        	case STRING: {
        		rootNode = new StringExpr((String)token.getValue());
        		token = nextToken();  // consume the string
        		break;
        	}
        	case TRUE:
        	case FALSE: {
        		rootNode = new BooleanExpr(tokenType.equals(LeolaTokenType.TRUE));
        		token = nextToken(); // consume the true or false
        		break;
        	}
        	case REAL:
        	case INTEGER: {        		
        		try {
        			Double d = null;
        			
        			Object value = token.getValue();
        			if ( value instanceof Integer) {
        				d = new Double( (Integer)value );
        			}
        			else {
        				d = new Double(value.toString());
        			}
        			
        			rootNode = new NumberExpr(d);
        			token = nextToken();  // consume the number
        		}
        		catch(Exception e) {
        			getExceptionHandler().errorToken(token, this, LeolaErrorCode.INVALID_NUMBER);
        		}
        		break;
        	}
        	case DEF: {
        		FuncDefExprParser parser = new FuncDefExprParser(this);
        		rootNode = parser.parse(token);
        		break;
        	}
        	case NEW: {
        		NewExprParser parser = new NewExprParser(this);
        		rootNode = parser.parse(token);
        		break;
        	}
        	case NULL: {
        		NullExprParser parser = new NullExprParser(this);
        		rootNode = parser.parse(token);
        		break;
        	}
            case NOT: {
                token = nextToken();  // consume the NOT

                // Parse the factor.  The NOT node adopts the
                // factor node as its child.
                ASTNode factorNode = parseFactor(token);
                
                // Create a NOT node as the root node.
                rootNode = new UnaryExpr( (Expr)factorNode, UnaryOp.NOT);
                break;
            }
            case BITWISE_NOT: {
                token = nextToken();  // consume the ~

                // Parse the factor.  The NOT node adopts the
                // factor node as its child.
                ASTNode factorNode = parseFactor(token);
                
                // Create a NOT node as the root node.
                rootNode = new UnaryExpr( (Expr)factorNode, UnaryOp.BIT_NOT);
                break;
            }
            case LEFT_PAREN: {
                token = nextToken();      // consume the (

                // Parse an expression and make its node the root node.
                rootNode = parseExpression(token);

                // Look for the matching ) token.
                token = currentToken();
                if (token.getType() == LeolaTokenType.RIGHT_PAREN) {
                    token = nextToken();  // consume the )
                }
                else {
                	getExceptionHandler().errorToken(token, this, LeolaErrorCode.MISSING_RIGHT_PAREN);                    
                }
                break;
            }
            case LEFT_BRACKET: {
            	ArrayDeclExprParser parser = new ArrayDeclExprParser(this);
            	rootNode = parser.parse(token);
            	break;
            }
            default: {
            	getExceptionHandler().errorToken(token, this, LeolaErrorCode.UNEXPECTED_TOKEN);
            }
        }

        return rootNode;
    }

    /**
     * Parse an identifier.
     * @param token the current token.
     * @return the root node of the generated parse tree.
     * @throws Exception if an error occurred.
     */
    protected ASTNode parseIdentifier(Token token)
        throws Exception
    {   	
    	ASTNode result = null;
    	
    	Token next = this.nextToken(); // eat the identifier name
    	
    	LeolaTokenType type = next.getType();
    	switch(type) {
    	case LEFT_PAREN: {
    		/* method invocation */
    		FuncInvocationParser parser = new FuncInvocationParser(this);
    		result = parser.parse(token);
    		break;
    	}
    	case LEFT_BRACKET: {
    		/* array index */
    		ArrayAccessExprParser parser = new ArrayAccessExprParser(this);    		
    		result = parser.parse(token);
    		break;
    	}
    	case DOT: {    	
    		MemberAccessParser parser = new MemberAccessParser(this);
    		result = parser.parse(token);
    		break;
    	}
    	case EQUALS: {
    		/* assignment */
    		AssignmentExprParser parser = new AssignmentExprParser(this);
    		result = parser.parse(token);
    		break;
    	}
    	default:
    		result = new VarExpr(token.getText());
    		break;    	
    	}
    	
    	setLineNumber(result, currentToken());
    	return (result);
    }

    
    /**
     * Parse an identifier.
     * @param token the current token.
     * @return the root node of the generated parse tree.
     * @throws Exception if an error occurred.
     */
    protected ASTNode parseChainedExpr(Token token)
        throws Exception
    {   	
    	ASTNode result = null;
    	    	    	
    	LeolaTokenType type = token.getType();
    	switch(type) {
    	case LEFT_PAREN: {
    		/* method invocation */    		
    		Expr[] params = ParserUtils.parseActualParameters(this, token, FuncInvocationParser.COMMA_SET, RIGHT_PAREN);    		
    		result = new ChainedFuncInvocationExpr(params);
    		break;
    	}
    	case LEFT_BRACKET: {
    		/* array index */
    		ArrayAccessExprParser parser = new ArrayAccessExprParser(this);    		
    		result = parser.parse(token);
    		break;
    	}
    	case DOT: {    	
    		ChainedMemberAccessParser parser = new ChainedMemberAccessParser(this);
    		result = parser.parse(token);
    		break;
    	}
    	case EQUALS: {
    		/* assignment */
    		AssignmentExprParser parser = new AssignmentExprParser(this);
    		result = parser.parse(token);
    		break;
    	}
    	default:
    		result = new VarExpr(token.getText());
    		break;    	
    	}
    	
    	setLineNumber(result, currentToken());
    	return (result);
    }
}
